﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Packaging;
using VsxFactory.Modeling.VisualStudio;
using System.Xml;
using System.Xml.Serialization;
using VsxFactory.Modeling.Strategies;

namespace VsxFactory.Modeling.Repository
{
    public class PackageBuilder : MarshalByRefObject
    {
        private const string DefaultContentType = "application/octet";

        public void Create(string manifestFileName, string packageFileName)
        {
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrWhiteSpace(manifestFileName));
            System.Diagnostics.Contracts.Contract.Requires(!String.IsNullOrWhiteSpace(packageFileName));
            
            string baseFolder = Path.GetDirectoryName(manifestFileName);
            PackageManifest manifest = null;
            try
            {
                using (var stream = File.OpenRead(manifestFileName))
                {
                    manifest = PackageHelper.DeserializeManifest(stream);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(String.Format("Unable to retrieve manifest from file {0}", manifestFileName), ex);
            }

            string mfn = Path.GetFileName(manifestFileName);
            if(!manifest.Files.Any(f => String.Compare(f, mfn, StringComparison.InvariantCultureIgnoreCase) == 0))
                manifest.Files.Add(mfn);

            using (System.IO.Packaging.Package package = System.IO.Packaging.Package.Open(packageFileName, FileMode.Create))
            {
                WriteFiles(baseFolder, manifest.Files, package);

                // Copy the metadata properties back to the package
                ValidateManifest(manifest);

                package.PackageProperties.Category = manifest.Kind;
                package.PackageProperties.Created = DateTime.Now;
                package.PackageProperties.Creator = String.Join(", ", manifest.Authors);
                package.PackageProperties.Description = manifest.Description;
                package.PackageProperties.Identifier = manifest.Name;
                package.PackageProperties.Version = manifest.Version.ToString();
                package.PackageProperties.Modified = package.PackageProperties.Created;
            }
        }

        private static void ValidateManifest(PackageManifest manifest)
        {
            Version result;
            if (String.IsNullOrEmpty(manifest.Kind))
                throw new Exception("Incorrect value for property 'Kind' in the manifest file. Valid values are : Strategy, ProjectTemplate, T4Template or custom value.");
            if (manifest.Authors == null)
                throw new Exception("Incorrect value for property 'Authors' in the manifest file");
            if (manifest.Version == null || Version.TryParse(manifest.Version, out result) == false)
                throw new Exception("Incorrect value for property 'Version' in the manifest file");
        }

        private static void WriteFiles(string baseFolder, List<string> files, System.IO.Packaging.Package package)
        {
            foreach (string file in files)
            {
                Uri uri = new Uri(Path.GetFileName(file), UriKind.Relative);
                Uri partUri = System.IO.Packaging.PackUriHelper.CreatePartUri(uri);
                if (!package.PartExists(partUri))
                {
                    string fullFileName = Path.Combine(baseFolder, file);
                    CreatePart(package, partUri, fullFileName);
                }
            }
        }

        private static void CreatePart(System.IO.Packaging.Package package, Uri partUri, string fullFileName)
        {
            System.IO.Packaging.PackagePart part = package.CreatePart(partUri, DefaultContentType, CompressionOption.Maximum);
            var outputStream = part.GetStream();
            using (Stream stream = File.OpenRead(fullFileName))
            {
                stream.CopyTo(outputStream);
            }
        }
    }
}
